\section{类与对象}
\subsection{构造函数}
C++的目标之一是让使用类对象和使用标准类型一样，然而，到现在为止不能像声明结构体一样声明一个类，因为结构体中的数据是公有的。而类对象中成员的访问是私有的，这时需要调用类的构造函数初始化。构造函数和函数头有一个特征——虽然没有返回值，但没有被声明为void类型。实际上，构造函数没有声明类型。

构造函数的参数表示的不是类成员，而是\textcolor{red}{赋值给类成员的值}。因此参数名不能和类成员相同，否则最终的代码会产生混乱。为了避免产生混乱，一种常见的做法是在数据成员名中使用m\_前缀或者使用\_后缀。

构造函数的调用通常有两种调用方式，一种是显式调用构造函数:\\
\cppinline{Stock food = Stock("World Cabbahe",250,1.25)}\\
另一种方式是隐式调用构造函数:\\
\cppinline{Stock garment("Furry Mason",50,2.5)}，这种格式更紧凑，等价于下面的显式调用：\\
\cppinline{Stock garment = new Stock("Furry Mason",50,2.5)}\\
每次创建类对象（甚至使用new动态分配内存）时，C++都使用类构造函数。下面是类构造函数和new一起使用的方法：\\
\cppinline{Stock *pstock = new Stock("Eletroshock Games",18,19.0);},这条语句创建一个Stock对象，将其初始化为参数提供的值，并将该对象的地址赋值给pstock指针，在这种情况下，\textcolor{red}{对象没有名称}，但是可以使用指针管理该对象。

默认构造函数是在没有提供显式初始值是用来创建对象的构造函数。也就是说，它用下面的声明的构造函数：
\cppinline{Stock fluffy_the_cat;}当且仅当没有定义任何构造函数的时候，编译器才会提供默认的构造函数。为类定义了构造函数后程序员就必须为它提供默认构造函数。如果提供了非默认构造函数(如\cppinline{Stock(const char * co,int n,double pr)})，但是没有提供默认构造函数，则下面的声明将出错：\\
\cppinline{Stock stock1}\\
公式股票代码,头文件件定义：
\cppfile{code/class/ex2/stock10.hpp}
类代码实现：
\cppfile{code/class/ex2/stock10.cc}
代码使用：
\cppfile{code/class/ex2/usestock2.cc}
程序代码中：\cppinline{Stock stock{"NanoSmart",12,20.0}}创建一个名为stock1的Stock对象，并将其数据成员初始化为指定值。

C++初始化列表，在C++ 中可以将列表初始化用于类:\\
\cppinline{Stock hot_tip = {"Derivatives Plus Plus",100,45.0}}\\
\cppinline{"sport Age Storage ,Inc"}\\
\cppinline{Stock temp {}}\\
在前面的两个声明中用括号扩起来的的列表和下面的构造函数匹配：\\
\cppinline{Stock::Stock(const std::String &co,long n = 0,double pr = 0.0)}
因此，将使用该构造函数来创建这两个对象。创建对象jock时第二个个第三个参数将为默认值0和0.0。第三个声明和默认构造函数匹配，因此将使用该构造函数创建对象temp。

另外，C++11还提供了名称为std::initialize\_list类，可以将其用作函数参数或方法参数的类型。这个类可以表示任意长度的列表，只要所有的列表项的类型都相同或者可转换成相同的类型。

\subsection{const成员函数}
请查看下面的代码：
\begin{cpp}
    const Stock land = Stock("Kludghourn Properties")};
    land.show();
\end{cpp}
对于当前的C++来说，编译器将拒绝执行第二行。这是因为show()的代码无法保证调用对象不被修改，调用对象和const一样，不应该被修改。我们以前通过将函数参数声明为const引用或指向const的指针来解决这种问题。但是这里存在语法问题：show()方法没有任何参数。相反，它所使用的对象是由方法调用隐式地提供。需要一种新的语法保证函数不会修改调用对象。C++的解决方法是将const放在函数的括号后面。也就是说，show()声明应该像这样:\\
\cppinline{void show() const}
同样函数定义的开头应该像这样:\\
\cppinline{void stock::show() const}\\
以这种方式声明和定义的类函数被称为const函数。就像应尽可能将const引用和指针用作函数形参一样，\textcolor{red}{只要类方法不修改调用对象，就应该将其声明为const}。

假设Bozo类构造函数的原型如下：\\
\cppinline{const char * fname,const char *lname}\\
可以用下面方式初始化对象:
\begin{cpp}
    Bozo botetta = bozo("Bozetta","Biggens");
    Bozo fufu("fufu","O'Dweeb");
    Bozo *pc = new Bozo("popo","le peu");
\end{cpp}
如果编译器支持C++11,则可以使用如下初始化:
\begin{cpp}
    Bozo bozetta = {"Botetta","Biggens"};
    Bozo fufu{"fufu","O'Dweeb"};
    Bozo *pc = new Bozo{"popo","le peu"}
\end{cpp}
\subsubsection{指针和const}
指针指向常量对象防止使用该指针修改所指向的值。指针本身申明为常量，这样可以防止改变指针指向的位置。
\begin{cpp}
    int age = 39;
    const int* pt = &age;//pt指向常量
\end{cpp}
pt指向const int，因此不能使用使用pt修改这个值。换句话来说，*pt的值为const，不能被修改：
\begin{cpp}
    *pt += 1;//不可修改
    vin>>*pt;//不可修改
\end{cpp}
现在看来是一个微妙的问题。pt的声明本身并不意味这它指向的值实际就是一个常量，而只是意味着对pt而言，这个值是常量。例如，pt指向age，而age不是const，可以通过改变age变量来修改age的值，但是决不能使用pt指针修改它：
\begin{cpp}
    *pt = 20//因为指向的是常量，不能修改
    age = 20;//可用，age不是常量
\end{cpp}
以前我们将常规变量的指针赋值给常规指针，而这里将常规变量的地址赋值给const的指针。因此还有两种可能：将const变量的地址赋值给const指针(可行)，将const的地址赋值给常规指针（不可行）。
\begin{cpp}
    const float g_earth = 93.8;
    const float* pe = &g_earch;//可用
    const float g_moom = 1.63;
    float* pm = &g_moon;//不可以
\end{cpp}
进入两级间接关系时，与一级间接关系一样将const和非const混合的指针方式将不再安全。
\begin{cpp}
    const int **pp2;
    int* p1;
    rconst int n = 13;
    pp2 = &p1;//不允许
    *pp2 = &n1;//可用
    *p1 = 10;//可用，但是改变const
\end{cpp}
\emph{
    如果数据本身并不是指针，则可以将const数据或者非const数据的地址赋值给const的指针，但只将非const数据的地址赋值给非const指针
}

假设一个const数据组成的数组：\cppinline{const int months[12] = {31,28,31,30,31,30}}，则禁止将\textcolor{red}{常量数组的地址}赋值给\textcolor{green}{非常量指针}将意味着不能将数组名作为参数传递给使用非常量形参的函数：
\begin{cpp}
    int sum(int arr[]，int n);//应该是常量int arr[]
    int j = sum(months,12);//不允许
\end{cpp}
上述函数调用将试图将const指针(months)赋值给非const指针()arr),编译器将禁止这种函数调用。
\cppfile{code/class/ex31/Coordinate.h}
\cppfile{code/class/ex31/Coordinate.cpp}
\cppfile{code/class/ex31/main.cpp}
\cppfile[firstline=9,lastline=12]{code/class/ex31/main.cpp}
分别声明常指针和常引用，表示其指向，或者代表的内容拥有读权限没有写权限，而在
\cppfile[firstline=1,lastline=16]{code/class/ex31/Coordinate.cpp}没有什么为const成员函数，所以成员函数需要的是读写权限的，这里调用将报错。
\cppfile[firstline=18,lastline=21]{code/class/ex31/Coordinate.cpp}表示定义的pCoor指向固定的地址且不能修改，所以20行将报错。
应该尽可能使用const的理由：
\begin{itemize}
    \item 这样可以避免由于无意间修改数据而导致的编程错误。
    \item 使用const使得函数能处理const和非const实参，否则将只能结合搜非const数据。
\end{itemize}
\begin{cpp}
    int age = 19;
    const int * pt = &age
\end{cpp}
第二个声明中的const只能防止修改pt指向的值age(这里是19)而不能防止修改pt的值，也就是说，可以将一个新地址赋值给pt：
\begin{cpp}
    int age = 50;
    pt = &asge;//可以指向另一个位置
\end{cpp}
但仍然不能使用pt修改它指向的值（现在为80）;第二种使用const的方式使得无法修改指针的值：
\begin{cpp}
    int sloth = 3;
    const int* ps = &sloth;//指向const int的指针
    int * const finger = &sloth;//指向int的const指针
\end{cpp}
最后一个声明中，关键字const的位置和以前不同。这种声明格式使得finger只能指向sloth，但允许使用finger来修改sloth的值。中间的申明不允许使用ps来修改sloth的值，但是允许将ps指向另一个位置。
\subsection{函数和二维数组}
为编写将二维数组作为参数的函数必须牢记，数组名被视为其地址，因此，相应的形参是一个指针，就像一维数组一样。比较难处理的是如何正确的声明指针。
\begin{cpp}
    int data[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int total = sum(data,3);
\end{cpp}
则sum()的原型是什么样的呢？函数为何将行数(3)作为参数而不是列数4作为参数？

data是一个数组名，该数组有三个元素。第一个元素本身是一个数组，数组包含4个int元素。因此data的类型是指向由4个int组成的数组的指针：因此正确的原型如下：\\*
\cppinline{int *ar[4];}\\
这里的括号必不可少，因为下面的声明将申明一个由4个指向int的指针组成的数组，而不是由一个指向由4个int组成的数组的指针：另外函数参数不能为数组：
\cppinline{int* ar2[4]}\\
还有另一种格式，这种格式和上述原型的含义完全相同，但可读性更强:
\mint{cpp}{int sum(int [][4],int size)}上面的连个个函数原型都指出，ar2是指针而不是数组。还需要注意的是，指针类型指出，它指向4个int组成的数组。因此，指针类型指定了列数，这就是没有将列数作为独立的函数参数进行传递的原因。由于指针类型指定了列数，这就是没有列数作为独立的函数参数进行传递的原因。由于指针类型指定了列数，因此sum()函数只能接受一个由4列组成的数组。但长度变量指定了行数，因此sum()对数组的行数没有限制:
\begin{cpp}
    int a[100][4];
    int b[6][4];
    int total1 = sum(a,100);//sum all of a
    int total2 = sum(b,6);//sum all of b
    int total3 = sum(a,10);//sum first 10 rows of a
    int total4 = sum(a+10,20);//sum next 20 rows if a
\end{cpp}
由于参数ar2是真相数组的指针，那么我们如何在函数定义中使用呢？最简单的办法是将ar2看做一个二维数组的名称。下面是一个可行的函数定义：
\begin{cpp}
    int sum(int ar2[][4],int size)
    {
    int total - 0;
    for(int r=0;r<size;r++)
    for(int c=0;c<4;c++)
    total +=ar2[r][c];
    return total;
    }
\end{cpp}
同样函数被传递给size参数，但无论是参数ar2的声明还是内部for循环中，列数都是固定的4列。可以使用数组表示法的原因如下：

由于ar2指向数组（他的元素由4个int数组组成的数组）的第一个元素(元素0)。因此表达式ar2+r指向编号为r的元素。因此ar2[r]是编号为r的元素。由于该元素本身就是一个由4个int组成的数组，因此ar2[r]是由4个int组成的数组的名称。将下标用于数组名将得到一个数组元素，因此ar2[r][c]是由4个int组成的数组中的一个元素，是一个int值。必须对指针ar2执行两次解除引用，才能得到数据。最简单的方法是使用方括号两次:ar2[r][c]。然而，如果不考虑难看的化，可以使用运算符\*\*两次:\cppinline{arr2[r][c] == *(*(ar2+r)+c)}

为了理解这点可以从内向外解析表达式的含义：
\begin{cpp}
    ar2 //指向第一行4个元素的指针
    ar2+r//指向r行4个元素的指针
    *(ar2+r)//指向r行4个元素数组的第r个元素ar2[r]
    *(ar2+r)+c//指r行c列的元素的指针ar2[r]+c
    *(*(ar2+r))+c//指向r行c列的元素的值ar2[r][c]
\end{cpp}
sum()的代码在声明参数ar2时，没有使用const，因为这种技术只能用于指向基本类型的的指针，而ar2是指向指针的指针。
\begin{cpp}
    #include <iostream>
    using namespace std;
    int sum(int data[][4],int r)
    {
    int result = 0;
    for(int i=0;i<r;i++)
    for(int j=0;j<4;j++)
    result +=data[i][j];
    return result;
    }
    int main()
    {
            int a[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
            cout<<sum(a,3)<<endl;
            cout<<"输出数组中第一个元素的地址: ";
            cout<<a<<endl;
            cout<<"输出数组中三个元素第2个元素的地址:";
            cout<<*a+1<<endl;
            cout<<"输出第1行第2个元素";
            cout<<*(*a+2)<<endl;
            cout<<"输出第2行第2个元素的值:";
            cout<<*(*(a+1)+1)<<endl;
            return 0;
        }

\end{cpp}
\begin{itemize}
    \item const int\* a = nullptr和int const \*a = nullptr完全等价（指向int const的指针）。
    \item int\* const p = nullptr,const指针。
    \item const int \* const p = nullptr和int const * const p = nullptr;完全等价指向const的const指针。
\end{itemize}
\subsection{this指针}
首先如何将两个要比较的对象提供给成员函数呢？假设该方法为topval(),则函数调用stock1.topval()将访问stock1对象的数据，而stock2.topval()将访问stock2对象的数据。如果希望该方法对两个对象比较，则必须将第二个对象作为参数传递给它。出于效率方面的考虑，可以按引用来传递参数，也就是说，topval()方法使用一个类型为const Stock \&的参数。

其次，如何将方法的答案传给调用程序呢？最直接的方法是让方法返回一个引用，该引用指向估价总值较高的对象。因此，用于比较的方法的原型如下：\\
\cppinline{const stock20 &(const * stock20 & s) const} \\
该函数隐式地访问一个对象，而显示地访问另一个对象，并返回其中一个对象的引用。括号中的const表明，该函数不会修改被显式访问的对象;而括号后面的const表明，该函数不会修改被隐式访问的对象。由于该函数返回了两个const对象中的一个的引用，因此返回类型应为const。
\cppfile{code/class/ex3/stock20.h}
\cppfile{code/class/ex3/stock20.cpp}
\cppinline{top = stock201.topval(stock2)}\\
\cppinline{top = stock201.topval(stock1)}\\
第一种隐式访问stock201,而显式访问stock202,第二种相关，无论那种方式都返回股价总额较高的对象。

在C++中使用this指针指向调用成员函数的对象（this被称为隐藏参数传递的范围）。这样函数调用stock201.(stock202)将this设置为stock20对象的地址，使得这个指针可以用于topval()方法。这样函数调用stock2.topval(stock1)将this设置为stock202对象的地址。一般来说，所有类方法都将this指针设置为调用它的对象的对峙。确实，topval()中total\_val只不过是this->total\_val的简写。
\cppfile{code/class/ex30/Point.h}
\cppfile{code/class/ex30/Point.cpp}
\cppfile{code/class/ex30/Line.h}
\cppfile{code/class/ex30/Line.cpp}
\cppfile{code/class/ex30/main.cpp}
\begin{remark}
    \item const 成员函数和普通成员函数互为重载函数。
    \item 调用const成员函数需要在const成员函数前加上const。
    \item 常成员函数void setX(int x) const;实质上等于void setX(Coordinate\* this,int x);
\end{remark}
\cppfile{code/class/ex33/main.cpp}
\subsection{对象数组}
\cppfile{code/class/ex3/main.cpp}
\subsection{类的作用域}
类中定义的名称（如类数据成员和类成员函数名）的作用域为整个类，作用于为整个类的名称只在该类中已知，在类外是不可知的。因此可以在不同的类中使用相同的类成员名而不引起冲突。
\subsection{作用域为类的常量}
有时候，使符号常量的作用于为类很有用。例如，类声明可能使用字面值30来指定数组的长度，由于该常量对于所有的对象来说都想同，因此创建一个有所有对想共享的常量是个不错的主意。下面的代码:
\begin{cpp}
    classBakery
    {
    private:
    const int Moths = 12;
    double costs{Months};
    }
\end{cpp}
但是这段代码行不通，因为声明类只是描述了对象的形式，并没有创建对象。因此，在创建对象前，将没有用于存储值的空间（实际上，C++提供了成员初始化，但是不适用于前述数组声明）。有两种方式可以实现这个目标，并且效果相同。

第一种方式在类中声明一个美剧。在类声明中声明的枚举的作用于是整个类，因此可以使用枚举为整形常量提供作用域为整个类的符号名称。也就是说可以这样声明：
\begin{cpp}
    class Bakery{
            private:
            enum{Months = 12};
            double const[Months]}
    }
\end{cpp}
注意这种方式声明枚举并不会创建类数据成员。也就是说，随哦又对想都不包含枚举。另外，Months只是一个符号名称，在作用域为整个类的代码中遇到它时，编译器将使用12替代它。

由于这里使用枚举只是为了创建符号常量，并不大算创建枚举类型的变量，因此不需要提供枚举名。在很多实现中ios\_base类在其共有部分中完成了类似的工作，诸如ops\_base::fixed等标识符就来自这里。其中fixed是ios\_base类中定义的典型的枚举量。

C++提供了另一种在类中定义常量的方式——使用关键字static:
\begin{cpp}
    class Bakery{
            private:
            static const int Months = 12;
            double const[Months]
        }
\end{cpp}
这将创建一个名字为Mohths的常量，该常量将和其它静态变量存储在一起，而不是存储在对象中。因此，只有一个Months常量，被所有的Bakery对象共享。

\subsection{作用于内枚举}
传统的枚举存在一些问题，其中之一是两个枚举定义中的枚举量可能发生冲突。假设有一个处理鸡蛋和T恤的项目，其中可能包含类似代码：
\begin{cpp}
    enum egg {Small,Medium,Large,Jumbo};
    enum t_shirt {Small,Medium,Large,Xlarge};
\end{cpp}
这将无法通过编译，因为egg Small和 t\_shirt Small位于相同的作用于中，它们将发生冲突。为了避免这还总问题，C++提供了一种新枚举，其枚举量的作用域为类。这种枚举的声明类似下面这样：
\begin{cpp}
    egg class egg{Small,Medium,Large,Jumbo};
    t_shirt Floyd = t_shirt::Large {Small,Medium,Large,Xlarge};
\end{cpp}
也可以使用关键字struct代替class。无论使用那种方式都需要使用枚举名来限定枚举量：
\begin{cpp}
    enum choice = egg::Large;
    t_shirt Floyd = t_shirt::Large;
\end{cpp}
枚举量的作用域为类后不同枚举定义中的枚举量就不会发生冲突。

C++还提高了作用域内枚举的优先权，在有些情况下，常规枚举将自动转换成整型，如将其赋值给int变量或用于比较表达式时，但是作用于内枚举不能隐式转换为整型:
\begin{cpp}
    enum egg_old{Small,Medium,Large,Jumbo};//unscope
    enum class t_shirt{Small,Medium,Large,Xlarge};//scope
    egg_old one = Medium;//unscoped
    t_shirt rolf = t_shirt::Large;//scoped
    int king = one;//implicit type conversion for unscoped
    int ring = rolf;//not allowed,no implicit type conversion
    if(king<Jumbo)//allow
    std::cout<<"Jumbo cpnverted to int before comparison.\n";
    if(king<t_shirt::Medium)
    tsd::cout<<"Not allowed:<not defined for scoped enum.\n"
    if(king<t_shirt::Medium)//not allow
    std::cout<<"Not allowed:< not defined for scoped enum.\n";
\end{cpp}
但是有必要的时候可以进行显示转换\cppinline{int Frodo = int(t_shirt::Small);//Frodo set to 0}

\subsection{抽象数据类型}
栈是一种容器，由可对它执行的操作来描述。
\begin{itemize}
    \item 可创建空栈
    \item 可将数据添加到栈顶
    \item 可从栈顶删除数据
    \item 可查看栈是否填满
    \item 可查看栈是否为空
\end{itemize}
定义头文件:
\cppfile{code/class/ex1/stack.hpp}
实现类方法:
\cppfile{code/class/ex1/Stack.cc}
使用类：
\cppfile{code/class/ex1/stacker.cc}
\section{运算符重载}
实现时间的加操作:
\cppfile{code/class/ex4/time.h}
\cppfile{code/class/ex4/time.cpp}
\cppfile{code/class/ex4/main.cpp}
重载“+”操作。
\cppfile{code/class/ex5/Time.h}
\cppfile{code/class/ex5/Time.cpp}
\cppfile{code/class/ex5/main.cpp}
代码中不仅能实现两个对象的相加，还能实现三个或者更多对象的相加。
\subsection{重载的限制}
\begin{enumerate}
    \item \textcolor{red}{重载后的运算符必须至少有一个操作数是用户自定义的类型}，这将防止用户为标准类型重载运算符。因此，不能将减去运算符(-)重载为计算两个double值的和，而不是它们的差。虽然这种限制将对创造性有所影响，但可以确保正序正常运行。
    \item 使用运算符时不能违反原来的句法规则。例如不能将\%(二元运算符)重载为使用一个操作数:
          \begin{cpp}
              int x;
              Time shiva;
              % x;//invalid for modules operator 
              % shiva;//invalid for overloaded operator 
          \end{cpp}
          同样，不能修改运算符的优先级。因此，如果将加号运算符重载成两个类相加，则新的运算符和原来的运算符有相同的优先级。
    \item 不能创建新的运算符。例如，不能定义operator**()函数表示求冪。
    \item 不能重载下面运算符：
          \begin{itemize}
              \item sizeof:sizeof运算符
              \item .:成员运算符
              \item .*：成员指针运算符
              \item ::作用于解析运算符
              \item ?:：条件运算符
              \item typeid:一个RTTI运算符
              \item const\_cast:强制类型转换运算符
              \item reinterpret\_cast:强制类型转换运算符
              \item static\_cast:强制类型转换运算符
          \end{itemize}

    \item 表\tabref{table:class1}中大多数的运算符都可以通过成员或者非成员函数进行重载，但是下面的运算符只能通过成员函数进行重载。
          \begin{itemize}
              \item =:赋值运算符
              \item ()：函数调用运算符
              \item []：下标运算符
              \item ->:通过指针访问类成员的运算符
          \end{itemize}

\end{enumerate}

\begin{table}[!ht]\label{table:class1}
    \centering
    \begin{tabular}{p{2cm}p{2cm}p{2cm}p{2cm}p{2cm}p{2cm}}
        \cppinline{+}   & \cppinline{-}  & \cppinline{*}   & \cppinline{/}      & \cppinline{                                                     %}& \cppinline{^}\\
        \cppinline{     & }              & \cppinline{|}   & \cppinline{~=}     & \cppinline{!}      & \cppinline{=}        & \cppinline{<}       \\
        \cppinline{>}   & \cppinline{+=} & \cppinline{-=}  & \cppinline{*=}     & \cppinline{/=}     & \cppinline{                                %=}\\
        \cppinline{^=}  & \cppinline{    & =}              & \cppinline{|=}     & \cppinline{<<}     & \cppinline{>>}       & \cppinline{>>=}     \\
        \cppinline{<<=} & \cppinline{==} & \cppinline{!=}  & \cppinline{<=}     & \cppinline{>=}     & \cppinline{          &                 & } \\
        \cppinline{||}  & \cppinline{++} & \cppinline{--}  & \cppinline{,}      & \cppinline{->*}    & \cppinline{->}                             \\
        \cppinline{()}  & \cppinline{[]} & \cppinline{new} & \cppinline{delete} & \cppinline{new []} & \cppinline{delete[]}                       \\
    \end{tabular}
\end{table}
实现时间的加，减，乘操作。
\cppfile{code/class/ex6/Time.hpp}
\cppfile{code/class/ex6/Time.cc}
\cppfile{code/class/ex6/main.cpp}
\subsection{友元函数}
C++控制对类对象私有部分的访问。通常，共有的类方法提供惟一的访问途径，但是这种限制太严格，以至于不太适合特定的编程问题。在这还纵情框下，C++提供了另一种形式的访问权限：\textcolor{red}{友元}。友元有三种：
\begin{itemize}
    \item 友元函数
    \item 友元类
    \item 友元成员函数
\end{itemize}
通过让函数成为类的友元，可以赋予该函数和类的成员函数相同的访问权限。上面重载的乘法和其它两种重载(+和-)的差别在于它使用了不同的类型。加法和减法都结合两个Time类型，但是乘法结合double和Time。，这限制了该操作的左侧是调用对象。如:\\
\cppinline{A = B*1.5}\\
将被转化为:\\
\cppinline{A = B.operator*(1.5)}而\cppinline{A = 1.5*B}则不行。解决办法之一是非成员函数。非成员函数不是由对象调用的，它使用的所有值（包括对象）都是显式参数。这样编译器能将下面的表达式：\\
\cppinline{A = 2.75*B}和下面的非成员函数调用匹配：\\
\cppinline{A = operator*(2.75,B)},该函数的原型如下：\\
\cppinline{A = operator*(double m,const Time & t);}\\
于当前的非成员函数重载运算符函数来说，\textcolor{red}{运算符表达式左边的操作数对应于运算符函数的第一个参数，运算符表达式右边的操作数对应于运算符函数的第二个参数。}而原来的成员函数则按相反的顺序处理操作数，也就是说，double诚意Time的值。

用非成员函数可以按所需要的顺序获得操作数（先是double然后是Time），但是引发了一个问题：非成员函数不能直接访问类的私有数据，至少常规非成员函数不能放问。然而，有一类特殊非成员函数可以访问类的私有成员，它们被称为友元函数。

\subsection{创建友元}
创建友元函数的第一步是将函数原型放在类声明中，并在原型前面加上friend：\\
\cppinline{friend Time operator*(double m,const Time &t);},该原型意味着下面两点：
\begin{itemize}
    \item 虽然operator*()函数实在类声明中声明的，但是它不是成员函数，因此不能使用成员函数运算符来调用
    \item 虽然operator*()函数不是成员函数，但它们和成员函数的访问权限相同。
\end{itemize}
第二步是编写函数定义。因为它们不是成员函数，所以不要使用Time::界定符。另外不要在定义中使用friend，定义如下：
\begin{cpp}
    Time Operator*(double m,const Time &t)
    {
            Time result;
            long totalminutes = t.hours*mult*60+t,minuts*mult;
            result.hours = totalminutes/60;
            result.minutes = totalminutes%60;
            return result;
        }
\end{cpp}
上述定义后，下面的语句：
\cppinline{A=1.5B}将转化为\cppinline{A = operator*(1.5,B)}。总之友元函数是非成员函数，其访问权限和成员函数相同。
下面实现<<运算符的重载:\\
\cppfile{code/class/ex7/Time.hpp}
\cppfile{code/class/ex7/Time.cc}
\cppfile{code/class/ex7/main.cpp}
下面实现名称空间VECTOR重载-运算符：\\
\cppfile{code/class/ex8/Vector.hpp}
\cppfile{code/class/ex8/Vector.cc}
\cppfile{code/class/ex8/main.cpp}
上面的代码是现在在Vector对象中存储矢量的直角坐标和极坐标，但公有接口并不依赖于这一事实。所有接口都只要求能显示这两种表示，并可以返回各个值。内部实现方式
可以完全不同。正如前面支出的，对象可以只存储x和y分量，而返回适量长度的magval()方法可以根据x和y的值计算出长度，而不是查找对象中存储的这个值。这种方法改变了实现，但是用户接口不变。\textcolor{red}{将接口和实现分离是OOP的目标之一，这样允许对实现进行调整，而无需修改使用这个类的程序中的代码。}

这两种实现各有利弊。存储数据意味着对象将占据更多的内存，每次Vector对象被修改的死后都是需要更新直角坐标和极坐标的表示：但查找数据的速度比较快。如果应用程序经常需要访问矢量这种数据表示，则这个例子采用的实现比较合适：如果只是偶尔需要使用极坐标，则另一种实现更好。可以再一个程序中使用一种实现，在另一个程序中使用另一种实现，但是它们的用户接口相同。
\subsection{全局友元函数}
定义类Time，在main中访问time的私有成员变量，打印输出。
类Time定义如下：
\cppfile{code/class/ex14/Time.h}
Time实现:
\cppfile{code/class/ex14/Time.cpp}
主函数：
\cppfile{code/class/ex14/main.cpp}
这里友元函数的声明放在TIme中任何任何地方都可以，不由private，public，protected约束。
\subsubsection{类友元}
Time.h头文件：
\cppfile{code/class/ex16/Time.h}
Time.cpp实现：
\cppfile{code/class/ex16/Time.cpp}
Show头文件：
\cppfile{code/class/ex16/Show.h}
Show实现：
\cppfile{code/class/ex16/Show.cpp}
main函数：
\cppfile{code/class/ex16/main.cpp}
\subsection{类的自动类型转换和强制类型转换}
\begin{remark}
    \begin{itemize}
        \item 友元关系不可以传递
        \item 友元关系的单向性
        \item 友元声明的形式及数量不受限制
    \end{itemize}

\end{remark}
\subsection{友元类的声明}
Show头文件：
\cppfile{code/class/ex15/Show.h}
Show实现：
\cppfile{code/class/ex15/Show.cpp}
Time头文件：
\cppfile{code/class/ex15/Time.h}
Time实现：
\cppfile{code/class/ex15/Time.cpp}
main：
\cppfile{code/class/ex15/main.cpp}
通过友元声明使用Show的成员函数访问Time类t的成员函数。
\subsection{静态数据成员}
\textcolor{red}{静态数据成员不依赖于对象，而是依赖于类。}
\begin{remark}
    \begin{itemize}
        \item 静态数据成员必须单独初始化。
        \item 静态成员函数不能调用非静态成员函数和非静态数据成员。
        \item 静态数据成员只有一份，并且不依赖对象而存在。
    \end{itemize}
\end{remark}
Tank数据头文件实现：
\cppfile{code/class/ex17/Tank.h}
Tank类实现：
\cppfile{code/class/ex17/Tank.cpp}
main函数实现：
\cppfile{code/class/ex17/main.cpp}
在静态成员函数中调用非静态成员函数是不可以的（如果在fire中调用getCount是不可以的）反之在非静态成员函数中调用静态成员函数是可以的。
\subsection{运算符重载}
\cppfile{code/class/ex18/Coordinate.cpp}
\cppfile{code/class/ex18/Coordinate.h}
\cppfile{code/class/ex18/main.cpp}
这里
\cppfile[firstline=16,lastline=20]{code/class/ex18/Coordinate.cpp}
代表一元运算符“-”函数的重载。
\cppfile[firstline=21,lastline=25]{code/class/ex18/Coordinate.cpp}
代表一元运算符“-”友元函数的重载。

\cppfile[firstline=31,lastline=36]{code/class/ex18/Coordinate.cpp}
代表二元运算符“obj++”友元函数的重载。
\cppfile[firstline=26,lastline=30]{code/class/ex18/Coordinate.cpp}
代表二元运算符“++obj”友元函数的重载。
\cppfile[firstline=44,lastline=49]{code/class/ex18/Coordinate.cpp}
代表二元运算符“[]”友元函数的重载。
\cppfile[firstline=50,lastline=52]{code/class/ex18/Coordinate.cpp}
代表输出运算符重载。
\subsection{函数模板}
\begin{itemize}
    \item 函数模板
          \cppfile{code/class/ex19/main.cpp}
    \item 参数模板

          \cppfile{code/class/ex20/main.cpp}
    \item 多参数模板
          \cppfile{code/class/ex21/main.cpp}
    \item
          \cppfile{code/class/ex22/main.cpp}

\end{itemize}
函数模板在没有实现之前不会产生任何代码，只是实现的时候会产生。
\begin{remark}
    \textcolor{red}{模板代码不能分离编译（所有代码都要写在.h文件中）}
\end{remark}
Array头文件（不能在class中实例化）：
\cppfile{code/class/ex23/Array.h}
\cppfile{code/class/ex23/main.cpp}
\subsection{程序组成}
内存分区：
\begin{itemize}
    \item 栈区：int x = 0;int *p = nullptr
    \item 堆区：int* p = new int[20]
    \item 全局区：存储全局变量及金泰变量
    \item 常量区：string str= "hello";
    \item 代码取：\textcolor{red}{存储逻辑代码的二进制}。
\end{itemize}
\textbf{初始化列表}
\begin{itemize}
    \item 初始化列表先于构造函数执行。
    \item 初始化列表能用于构造函数。
    \item 初始化列表可以同时初始化多个数据成员。
\end{itemize}
\begin{remark}
    如果类成员中有const常量，则不能在构造函数中初始化，只能使用初始化列表初始化。
\end{remark}
\cppfile{code/class/ex24/Teacher.h}
\cppfile{code/class/ex24/Teacher.cpp}
\cppfile{code/class/ex24/main.cpp}
\subsection{拷贝构造函数}
\begin{itemize}
    \item 如果没有自定义拷贝构造函数则系统自动生成一个默认的拷贝构造函数。
    \item 当采用直接初始化或者赋值初始化实例化对象时，系统自动调用拷贝构造函数。
\end{itemize}

\cppfile[firstline=25,lastline=27]{code/class/ex24/Teacher.cpp}
\begin{remark}
    拷贝构造函数不能重载。当函数的形参是类的对象，调用函数时，进行形参与实参结合时使用。这时要在内存新建立一个局部对象，并把实参拷贝到新的对象中。理所当然也调

    用拷贝构造函数。还有一点，为什么拷贝构造函数的参数需要是引用？ 这是因为如果拷贝构造函数中的参数不是一个引用，即形如CClass(const

    CClass c\_class)，那么就相当于采用了传值的方式(pass-by-value)，而传值的方式会调用该类的拷贝构造函数，从而造成无穷递归地调用拷贝构造函

    数。
    当函数的返回值是类对象，函数执行完成返回调用者时使用。也是要建立一个临时对象，再返回调用者。为什么不直接用要返回的局部对象呢？

    因为局部对象在离开建立它的函数时就消亡了，不可能在返回调用函数后继续生存，所以在处理这种情况时，编译系统会在调用函数的表达式中创建一

    个无名临时对象，该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象，实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果

    返回的是变量，处理过程类似，只是不调用构造函数。
\end{remark}
\subsection{对象成员}
对象成员构造与系构顺序相反。
\cppfile{code/class/ex26/Node.h}
\cppfile{code/class/ex26/Node.cpp}
\cppfile{code/class/ex26/Line.h}
\cppfile{code/class/ex26/Line.cpp}
\cppfile{code/class/ex26/main.cpp}
实现Line对象的时候先构造其成员对象Node,start和end，销毁时线销毁Line，然后是Node start和end。
\subsection{指针对象}
头文件：
\cppfile{code/class/ex28/Coordinate.h}
实现：
\cppfile{code/class/ex28/Coordinate.cpp}
\cppfile{code/class/ex28/main.cpp}
\subsection{对象成员指针}
\cppfile{code/class/ex29/Line.h}
\cppfile{code/class/ex29/Line.cpp}

\cppfile{code/class/ex29/Point.h}
\cppfile{code/class/ex29/Point.cpp}
\cppfile{code/class/ex29/main.cpp}
\subsection{引用}
指针类型的引用：类型\*\&指针引用名=指针;
\cppfile{code/class/ex32/main.cpp}
C++不自动转换不兼容的类型。例如，下面的语句是非法的:\\
\cppinline{int * p =10;}\\
虽然计算机内部可能使用整数表示地址，但从概念上说，整数和指针完全不同。例如，不能计算指针的平方。然而，在无法自动转换时，可以使用强制类型转换：\\
\cppinline{int *p = (int *)10}\\
上面的语句将10强制转换成int指针类型(int *类型)，将指针设置为地址10。这种赋值是否有意义是另一回事。
\section{类继承}
派生的对象存储了基类的数据成员和基类方法。派生类需要自己的构造函数，可以根据需要添加额外的数据成员和成员函数，\textcolor{red}{其构造函数必须给新成员（如果有的话）和继承的成员提供数据}。

下面实现tabletennis类。头文件定义如下：
\cppfile{code/class/ex10/tabtenn.hpp}
类实现代码：
\cppfile{code/class/ex10/tabtenn.cc}
现在在原来的类的基础上派生出新的类RatedPlayer，新的类有新的数据成员rate和方法Rating,ResetRating。
下面为新的类的头文件：
\cppfile{code/class/ex10/RatedPlayer.hpp}
类定义：
\cppfile{code/class/ex10/RatedPlayer.cc}
测试代码：
\cppfile{code/class/ex10/main.cpp}
派生的构造函数RatedPlayer必须调用基类TableTennies的构造函数。主要代码有两种构造方法：
\cppfile[firstline=7,lastline=9]{code/class/ex10/RatedPlayer.cc}
这段代码通过包含TableTennisPlayer类的头文件，在实现构造函数的同时给基类对象TableTennisPlayer初始化,同时在代码中给新的成员变量rating初始化。
\cppfile[firstline=10,lastline=12]{code/class/ex10/RatedPlayer.cc}
这段使用TableTennisPlayers实例tp的引用初始化基类对象，调用的时候需要传入实例和初始化新的成员变量rating。
派生类和基类之间的关系：
\begin{enumerate}
    \item 派生类能使用基类的共有或者保护方法。
    \item 基类引用可以在不进行显式转换的情况下引用派生类对象。如
          \cppfile[firstline=12,lastline=13]{code/class/ex10/main.cpp}
          但是不能使用派生类的方法(rating)。
    \item 不可以将基类对象和地址赋值给派生类引用和指针。
\end{enumerate}
\subsection{继承}
\begin{itemize}
    \item 公有继承：派生类对象可是基类对象，可以对基类对象执行任何操作，也可以对派生类对象执行。
    \item 保护继承：在类外只能用公有类成员来访问protected部分中的类成员。Private和protected之间的区别只有在基类派生的类中才会表现，派生类的成员能直接访问基类的保护成员，但是不能直接访问基类的私有成员。

\end{itemize}
下面定义一个类Brass，实现存款Deposit，取款Withdraw,获取余额Balance，查看账户信息ViewAcct。函数头文件：
\cppfile{code/class/ex11/Brass.hpp}
函数代码实现：
\cppfile{code/class/ex11/Brass.cc}
\cppfile[firstline=18,lastline=20]{code/class/ex11/Brass.hpp}
定义了三个虚函数，虚函数表明函数会选择使用基类或者派生类的方法。如果方法通过引用或者指针而不是对象调用，虚函数将确定使用哪种方法。\textcolor{red}{如果没有关键字virtual，程序将根据引用类型或指针类型选择方法如果使用了virtual，程序将根据引用或者指针指向的对象的类型来选择方法。}
如果ViewAcct()不是虚函数，则函数的行为如下：
\begin{cpp}
    Brass dom("Dominic Branker",11224,4183.45);
    BrassPlus dot("Dorothy Banker",12118,2592.00);
    Brass &b1_ref = dom;
    Brass &b2_ref = dot;
    b1_ref.ViewAcct();        //使用Brass::ViewAcct
    b2_ref.ViewAcct();        //使用Brass::ViewAcct
\end{cpp}
如果是虚函数则行为如下：
\begin{cpp}
    Brass dom("Dominic Branker",11224,4183.45);
    BrassPlus dot("Dorothy Banker",12118,2592.00);
    Brass &b1_ref = dom;
    Brass &b2_ref = dot;
    b1_ref.ViewAcct();        //使用Brass::ViewAcct
    b2_ref.ViewAcct();        //使用BrassPlus::ViewAcct
\end{cpp}
\cppfile{code/class/ex35/main.cpp}
上面的代码中如果不给基类Shape的析构函数添加virtual关键字将导致Circlr中在在堆中分配的co内存空间无法释放。即析构函数
\cppfile[firstline=79,lastline=79]{code/class/ex35/main.cpp}调用的是基类的析构函数而派生类中分配的co无法释放导致内存泄漏。给基类Shape的析构函数加上virtual就能解决该内存泄漏问题。
\begin{remark}[Virtual使用限制]
    \begin{itemize}
        \item 普通函数不能是虚函数
        \item 静态(static)成员函数不能是虚函数。
        \item 不能修饰内联函数，inline virtual int eat()会被认为是virtual int eat()。
        \item 构造函数不能为虚函数。
    \end{itemize}
\end{remark}
\subsection{多继承和多重继承}
\begin{remark}
    \begin{itemize}
        \item 多继承：是一个类从多个类同时继承。如农民工从农民和工人继承。
        \item   多重继承：一个类从一个类继承，继承的类又被继承，老师从人继承，教授从老师继承。
    \end{itemize}
\end{remark}
产生多态的基础是继承关系，没有继承就没有多态。多态的语法核心是用virtual关键字使用多个类建立多态关系。
方法在基类中声明为虚后，在派生类中将自动成为虚方法。\textcolor{red}{基类声明了一个虚系构函数能确保释放对象时按照正确的顺序调用析构函数。}
\emph{如果需要在派生类中重新定义基类的方法，则将它设置为虚方法否则设置为非虚方法。}
\subsection{虚函数的工作原理}
编译器处理虚函数的方法是：给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为\textbf{虚函数表}。虚函数表中存储了为类对象进行声明的虚函数的地址。基类对象包含一个指针，该指针指向所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的定义，该虚函数将保存新函数的地址，如果派生类没有重新定义虚函数，该虚函数表将保存函数原始版本的地址。

调用虚函数的时候，程序将查看存储在对象中的虚函数表地址，然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数，则程序将使用数组中的第一个函数地址，并指向具有该地址的函数。如果使用类声明中的第三个虚函数，程序将使用地址为数组中第三个元素的函数。使用虚函数将产生一些成本：
\begin{itemize}
    \item 每个对象都将增大，增大量为存储地址的空间。
    \item 对于每个类，编译器都创建一个虚函数地址表（数组）。
    \item 对于每个函数调用，都需要执行一项额外的操作——到表中查找地址。
\end{itemize}
\subsection{虚函数的注意事项}
\begin{itemize}
    \item 在基类方法的声明中使用关键字virtual可以使该方法在基类及所有的派生类（包括从派生类派生出的类）中是虚的。
    \item 如果使用指向对象的引用或者指针调用虚方法，程序将使用为对象类型定义的方法，而不使用为引用或者指针类型定义的方法。这称为\textbf{动态联编}。这种行为非常重要，因为这样基类指针或者引用可以指向派生类对象。
    \item 如果定义的类将被用作基类，则应该\textcolor{red}{将那些要在派生类中重新定义的方法声明为虚的。}
\end{itemize}
关于虚函数：
\begin{enumerate}
    \item \textcolor{red}{构造函数不能是虚函数。}创建派生类对象的时候将调用派生类的构造函数而不是基类的构造函数，然后，派生类的构造函数将拥有基类的构造函数。
    \item \textcolor{red}{析构函数应当是虚函数，除非类不用做基类。}例如：假设Employee是基类，Singer是派生类，然后，定义char *成员，该成员指向由new分配的内存。当Singer对象过期的时候，必须调用~Singer()析构函数来释放内存。
          \begin{cpp}
              Employee *pe = new Singer;
              delete pe;
          \end{cpp}
          如果使用静态联编，delete的语句将调用~Employee()析构函数，这将释放由Singer对象中的Employee部分指向的内存，但是不会释放新的类成员指向的内存。如果析构函数是虚的，则上述代码将调用~Singer析构函数释放Singer组件指向的内存。这意味着，即使基类不需要显示析构函数提供服务，也不应依赖于默认构造函数，而应该提供虚析构函数，即时它们不做任何操作。

          \emph{通常应该定义一个虚析构函数，即使并不需要析构函数。}
    \item \textcolor{red}{友元不能使虚函数，因为友元不是类成员，只有成员才能是虚函数。}
    \item 如果派生类没有重新定义成员函数则不需要声明为虚函数。
    \item \textcolor{red}{重新定义将隐藏方法}，假设创建了如下代码：
          \begin{cpp}
              class Dwelling
                  {
                      public:
                      virtual void showperks(int a) const;
                  }
              class Hovel:public Dwelling
                  {
                      public:
                      virtual void showperks() const;
                  }
          \end{cpp}
          这段代码可能会引起警告。
          \begin{cpp}
              Hovel trump;
              trump:showperks();//合法
              trump:showperks(4);//不合法，带参数int的基类方法被隐藏了
          \end{cpp}
          重新定义继承的方法并不是重载。如果重新定义派生类中的函数，将不只是使用相同的函数参数列表覆盖基类声明，\textcolor{red}{无论参数列表是否相同，该操作将隐藏所有的同名基类方法。}
          两条经验规则：
          \begin{enumerate}
              \item 如果重新定义继承的方法，应确保和原来的原型完全相同，但是如果返回类型是基类引用或者指针，则可修改为指向派生类的引用或者指针。这种特征称为\textbf{返回类型协变}，因为允许返回类型随类类型的变化而变化：
                    \begin{cpp}
                        class Dwelling{
                                public:
                                virtual Dwelling & build(int n)
                            }
                        class Hovel:public Dwelling
                            {
                                virtual Hovel & build(int n);
                            }
                    \end{cpp}
              \item 如果基类声明被重载了，应该在派生类中重新定义所有的基类版本。
                    \begin{cpp}
                        class Dwelling
                            {
                                public:
                                virtual void showperkers();
                                virtual void Showperkers(int a) const;
                                virtual void Showperkers(double a) const;
                                virtual void showperkers() const;
                            };
                        class Hovel:public Dwelling
                            {
                                public:virtual void showperks(int a) const;
                                public:virtual void showperks(double a) const;
                                public:virtual void showperks() const;
                            }
                    \end{cpp}
          \end{enumerate}
\end{enumerate}
\subsection{纯虚函数}
有时候定义的类之间有相交部分，这时候最好的方法是使用抽象基类将两个类的公共部分抽象出来，然后两个类分别从公共部分继承。C++通过使用纯虚函数提供未实现的函数。纯虚函数在函数声明的结尾处为=0。当类声明中包含纯虚函数时，则不能创建该类对象。这里的理念是，包含纯虚函数的类只能作为基类，要成为真正的抽象基类，必须至少包含一个纯虚函数。函数原型中的=0使虚函数成为纯虚函数。但是C++甚至允许纯虚函数有定义。
\begin{itemize}
    \item 包含纯虚函数的类称为抽象类，不能实例化。
    \item 从抽象类继承的子类必须实例化父类的所有纯虚函数之后才能实例化。
\end{itemize}
\cppfile{code/class/ex36/main.cpp}
上面的例子中实例化Worker类的时候需要实现抽象类Person的work方法之后才能执行其work()方法。
\subsection{接口类}
仅含有纯虚函数的类称为接口类。
\cppfile{code/class/ex37/main.cpp}
\subsection{RTTI}
dynamic\_cast:
\begin{itemize}
    \item 只能用于指针和引用的转换
    \item 要转换的类型中必须包含虚函数
    \item 转换成功返回的是对象的地址，否则为nullptr。
\end{itemize}
type\_id：注意事项
\begin{itemize}
    \item type\_id返回一个type\_info对象的引用
    \item 如果想通过基类的指针获得派生类的数据类型，基类必须带有虚函数。
    \item 只能获取对象的实际类型。
\end{itemize}
type\_info的内容：
\begin{cpp}
    class type_info(){
            public:
            const char* name() const;
            bool operator==(const type_info&rhs) const;
            bool operator!=(const type_info&rhs) const;
            int before(const type_info& rhs)const;
            virtual ~type_info();
        }
\end{cpp}
\cppfile{code/class/ex38/main.cpp}
\subsection{异常处理}
\cppfile{code/class/ex39/main.cpp}
